﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Test3D_ohne_pad
{
        class Map
        {
            //vertexe beinhalten farbe, position, normal (für lichteffekt)
            public struct VertexPositionColorNormal
            {
                public Vector3 Position;
                public Color Color;
                public Vector3 Normal;

                public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
                (
                    new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
                    new VertexElement(sizeof(float) * 3, VertexElementFormat.Color, VertexElementUsage.Color, 0),
                    new VertexElement(sizeof(float) * 3 + 4, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0)
                );
            }

            private string name;

            private Texture2D heightMap;
            BasicEffect effect;
            GraphicsDevice device;

            private Matrix worldMatrix;
            private Matrix viewMatrix;
            private Matrix projectionMatrix;

            private VertexPositionNormalTexture[] vertices;
            private int[] indices;

            private int terrainWidth;
            private int terrainHeight;
            private float[,] heightData;

            private float move = 0.0f;

            VertexBuffer deviceVertexBuffer;
            IndexBuffer deviceIndexBuffer;

            Texture2D grassTexture;


            //Konstruktor
            public Map(string mapName, Texture2D heightMap, GraphicsDevice device, Texture2D texture, Matrix view, Matrix projection)
            {
                name = mapName;
                this.effect = new BasicEffect(device);
                this.heightMap = heightMap;
                this.device = device;
                this.grassTexture = texture;

                LoadHeightData(heightMap);
                SetUpVertices();
                SetUpIndices();
                CalculateNormals();
                SetUpCamera(view, projection);

                CopyToBuffers();
            }


            public string Name
            {
                get { return name; }
            }


            public void DrawMap()
            {
                //ZBuffer leeren
                //device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0f, 0);

                device.Indices = deviceIndexBuffer;
                device.SetVertexBuffer(deviceVertexBuffer);






                //alle dreiecke zeichnen, auch die die gegen den uhrzeigersinn                  (im spiel dann löschen!!!!)
                RasterizerState rs = new RasterizerState();
                rs.CullMode = CullMode.None;
                //Dreiecke nicht ausfüllen
                //rs.FillMode = FillMode.WireFrame;
                device.RasterizerState = rs;

                //Effekt setzen
                effect.TextureEnabled = true;
                effect.Texture = grassTexture;
                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Apply();
                }
                //world, view und projection matrizen den effekt übergeben
                effect.View = viewMatrix;
                effect.Projection = projectionMatrix;
                effect.World = worldMatrix;

                //Licht einschalten
                effect.EnableDefaultLighting();
                effect.SpecularPower = 50f;

                //Zeichnen
                device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
            }


            //Höhendaten aus der Heightmap auslesen und im HeightData array speichern
            private void LoadHeightData(Texture2D heightMapTexture)
            {
                terrainWidth = heightMapTexture.Width;
                terrainHeight = heightMapTexture.Height;

                Color[] heightMapColors = new Color[terrainWidth * terrainHeight];

                heightMapTexture.GetData(heightMapColors);

                //height array mit den farbwerten für rot (abgescchwächst, deshalb / 5) befüllen
                heightData = new float[terrainWidth, terrainHeight];
                for (int x = 0; x < terrainWidth; x++)
                {
                    for (int y = 0; y < terrainHeight; y++)
                    {
                        heightData[x, y] = heightMapColors[x + (y * terrainWidth)].R / 5;
                    }
                }
            }


            //Vertexes erstellen
            private void SetUpVertices()
            {
                //Höhenzonen
                float minHeight = float.MaxValue;
                float maxHeight = float.MinValue;
                for (int x = 0; x < terrainWidth; x++)
                {
                    for (int y = 0; y < terrainHeight; y++)
                    {
                        if (heightData[x, y] < minHeight)
                            minHeight = heightData[x, y];
                        if (heightData[x, y] > maxHeight)
                            maxHeight = heightData[x, y];
                    }
                }


                //vertexe erstellen, position und farbe
                vertices = new VertexPositionNormalTexture[terrainWidth * terrainHeight];
                for (int x = 0; x < terrainWidth; x++)
                {
                    for (int y = 0; y < terrainHeight; y++)
                    {
                        vertices[x + (y * terrainWidth)].Position = new Vector3(x, heightData[x, y], -y);
                        vertices[x + y * terrainWidth].TextureCoordinate.X = (float)x / 30.0f;
                        vertices[x + y * terrainWidth].TextureCoordinate.Y = (float)y / 30.0f;




                        /*
                        //Färben
                        if (heightData[x, y] < minHeight + (maxHeight - minHeight) / 4)
                            vertices[x + y * terrainWidth].Color = Color.DarkSlateBlue;
                        else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 2 / 4)
                            vertices[x + y * terrainWidth].Color = Color.DarkGreen;
                        else if (heightData[x, y] < minHeight + (maxHeight - minHeight) * 3 / 4)
                            vertices[x + y * terrainWidth].Color = Color.Brown;
                        else
                            vertices[x + y * terrainWidth].Color = Color.White;
                         */
                    }
                }
            }


            //legt Indizes der vertexes fest
            private void SetUpIndices()
            {
                indices = new int[(terrainWidth - 1) * (terrainHeight - 1) * 6];
                int counter = 0;
                for (int y = 0; y < terrainHeight - 1; y++)
                {
                    for (int x = 0; x < terrainWidth - 1; x++)
                    {
                        int lowerLeft = x + y * terrainWidth;
                        int lowerRight = (x + 1) + y * terrainWidth;
                        int topLeft = x + (y + 1) * terrainWidth;
                        int topRight = (x + 1) + (y + 1) * terrainWidth;

                        indices[counter++] = topLeft;
                        indices[counter++] = lowerRight;
                        indices[counter++] = lowerLeft;

                        indices[counter++] = topLeft;
                        indices[counter++] = topRight;
                        indices[counter++] = lowerRight;


                    }
                }
            }


            //Kamera und Projektionseinstellungen
            private void SetUpCamera(Matrix view, Matrix projection)
            {
                viewMatrix = view;
                projectionMatrix = projection;
                worldMatrix = Matrix.CreateTranslation(new Vector3(-terrainWidth / 2, 0, terrainWidth / 2));
            }


            private void UpdateWorldMatrix()
            {
                worldMatrix = Matrix.CreateTranslation(new Vector3(0, 0, move)) * Matrix.CreateTranslation(new Vector3(-terrainWidth / 2, 0, terrainWidth / 2));

            }

            private void CalculateNormals()
            {
                for (int i = 0; i < vertices.Length; i++)
                    vertices[i].Normal = new Vector3(0, 0, 0);

                for (int i = 0; i < indices.Length / 3; i++)
                {
                    int index1 = indices[i * 3];
                    int index2 = indices[i * 3 + 1];
                    int index3 = indices[i * 3 + 2];

                    Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                    Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                    Vector3 normal = Vector3.Cross(side1, side2);

                    vertices[index1].Normal += normal;
                    vertices[index2].Normal += normal;
                    vertices[index3].Normal += normal;


                }
                for (int i = 0; i < vertices.Length; i++)
                    vertices[i].Normal.Normalize();
            }

            //Daten in speicher der grafikkarte laden
            private void CopyToBuffers()
            {
                deviceVertexBuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
                deviceVertexBuffer.SetData(vertices);

                deviceIndexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly);
                deviceIndexBuffer.SetData(indices);
            }

            public void update(Matrix view)
            {
                viewMatrix = view;
            }

        }
    }

